/*
 *             Copyright (C) 2023 Steve Jrong
 *
 * 	   GitHub Homepage: https://www.github.com/SteveJrong
 *      Gitee Homepage: https://gitee.com/stevejrong1024
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.stevejrong.groovy.shell

import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.stream.Collectors

/**
 * Generate redis conf file in redis cluster.
 *
 * Create by: SteveJrong
 */
/*
    example execute command:
    groovy generate_file.groovy ---port1=6379 \
                                ---port2=16379 \
                                ---ip=192.168.56.11 \
                                ---pass=123456 \
                                ---config-path=node.conf \
                                --source-path=config-files/redis-cluster/redis.conf.template.old \
                                --target-path=podman-instance-data/redis-cluster/redis-cluster-s11-n1/conf/redis.conf

    tips: '--' means fixed parameters(must) of generate file
          '---' means dynamic parameters of generate file
 */
static void main(String[] args) {
    GenerateResourcesAction.doAction(args)
}

final class GenerateResourcesAction {

    static void doAction(String[] args) {
        // Build param list
        def fixedParamsList = ShellUtil
                .getParamListByType(ShellUtil.ParamType.FIXED, args)
        def dynamicParamsList = ShellUtil
                .getParamListByType(ShellUtil.ParamType.DYNAMIC, args)

        // Generate final string using param lists
        def templateFilePath = ShellUtil
                .getValueByNameInParamList(ShellUtil.ParamKey.SOURCE_PATH.val, fixedParamsList)

        def finalContent = ''
        /*
            Determine whether the path of the template file is a directory or a file
            If it is a directory, it is necessary to read three files and enable the append mode of the body template,
            otherwise only one file will be read
         */
        def hasDirectory = FileUtil.hasDirectoryPath(templateFilePath)
        if (hasDirectory) {
            def targetFileExists = FileUtil.fileExists(ShellUtil
                    .getValueByNameInParamList(ShellUtil.ParamKey.TARGET_PATH.val, fixedParamsList))
            if (targetFileExists) {
                // If the final file has been generated, append body.template
                def sourceContent = FileUtil.readFileToString(ShellUtil
                        .getValueByNameInParamList(ShellUtil.ParamKey.TARGET_PATH.val, fixedParamsList))
                def bodyContent = FileUtil.readFileToString(templateFilePath + File.separatorChar + ShellUtil.TemplateFileKey.BODY_TEMPLATE.val)
                def finalBodyContent = ShellUtil.replaceParams(bodyContent, dynamicParamsList)

                def lastBDSymbolIndex = sourceContent.lastIndexOf('#BD') + 3
                finalContent = new StringBuilder(sourceContent).insert(lastBDSymbolIndex, '\n' + finalBodyContent).toString()
            } else {
                // Create a new final file
                def headContent = FileUtil.readFileToString(templateFilePath + File.separatorChar + ShellUtil.TemplateFileKey.HEAD_TEMPLATE.val)
                def bodyContent = FileUtil.readFileToString(templateFilePath + File.separatorChar + ShellUtil.TemplateFileKey.BODY_TEMPLATE.val)
                def footContent = FileUtil.readFileToString(templateFilePath + File.separatorChar + ShellUtil.TemplateFileKey.FOOT_TEMPLATE.val)

                def finalHeadContent = ShellUtil.replaceParams(headContent, dynamicParamsList)
                def finalBodyContent = ShellUtil.replaceParams(bodyContent, dynamicParamsList)
                def finalFootContent = ShellUtil.replaceParams(footContent, dynamicParamsList)
                finalContent = finalHeadContent + finalBodyContent + finalFootContent
            }
        } else {
            def sourceContent = FileUtil.readFileToString(templateFilePath)
            finalContent = ShellUtil.replaceParams(sourceContent, dynamicParamsList)
        }

        // create file
        FileUtil.generateFile(finalContent, ShellUtil
                .getValueByNameInParamList(ShellUtil.ParamKey.TARGET_PATH.val, fixedParamsList), FileUtil.FileType.FILE)
    }
}

/**
 * Shell util class.
 */
final class ShellUtil {

    static enum ParamType {
        DYNAMIC('---'), FIXED('--')

        private String val

        ParamType(String val) {
            this.val = val
        }
    }

    static enum ParamKey {
        SOURCE_PATH('source-path'), TARGET_PATH('target-path')
        private String val

        ParamKey(String val) {
            this.val = val
        }

        String getVal() {
            return val
        }
    }

    static enum TemplateFileKey {
        HEAD_TEMPLATE('head.template'), BODY_TEMPLATE('body.template'), FOOT_TEMPLATE('foot.template')
        private String val

        TemplateFileKey(String val) {
            this.val = val
        }

        String getVal() {
            return val
        }
    }

    /**
     * Obtain parameter sets from commands executing scripts
     *
     * @param paramType
     * @param args
     * @return
     */
    static List getParamListByType(ParamType paramType, String... args) {
        return Arrays.stream(args)
                .map { str ->
                    {
                        String[] splitArray = str.split('=') // { ---port1, 6379 }
                        String first = splitArray[0] // ---port1
                        String last = splitArray.length > 1 ? splitArray[1] : '' // 6379

                        int prefixCharTotalCount = first.toList().stream()
                                .limit(3)
                                .filter { str1 -> str1.equals('-') }
                                .collect(Collectors.counting()).intValue() // count('---') or count('--')
                        String prefix = (prefixCharTotalCount == ParamType.DYNAMIC.val.length())
                                ? ParamType.DYNAMIC.val : ParamType.FIXED.val

                        String name = first.substring(prefix.length(), first.length())
                        return new ParamDTO(prefix, name, last)
                    }
                }.filter { paramDTO -> paramDTO.prefix.equals(paramType.val) } // filter collection by parameters type
                .collect(Collectors.toList())
    }

    private static final class ParamDTO {
        String prefix
        String name
        String value

        ParamDTO(String prefix, String name, String value) {
            this.prefix = prefix
            this.name = name
            this.value = value
        }
    }

    static String replaceParams(String sourceContent, List<ParamDTO> paramList) {
        paramList.stream().each { paramDTO ->
            sourceContent = sourceContent.replaceAll(paramDTO.prefix + paramDTO.name, paramDTO.value)
        }

        return sourceContent
    }

    static String getValueByNameInParamList(String name, List<ParamDTO> paramDTOList) {
        for (ParamDTO paramDTO : paramDTOList) {
            if (paramDTO.name.equals(name)) {
                return paramDTO.value
            }
        }
        return null
    }
}

/**
 * File util class.
 */
final class FileUtil {

    enum FileType {
        FILE, DIRECTORY

        private String val

        FileType() {
        }

        FileType(String val) {
            this.val = val
        }

        String getVal() {
            return val
        }
    }

    static String readFileToString(String filePath) {
        def result = new StringBuilder()
        new File(filePath).eachLine {
            item -> result.append(item).append("\n")
        }
        return result.toString()
    }

    /**
     *
     * @param content
     * @param targetFilePath
     * @param fileType
     */
    static void generateFile(String content, String targetFilePath, FileType fileType) {
        regenerateFile(targetFilePath, fileType)
        new File(targetFilePath).write(content)
    }

    /**
     *
     * @param filePath
     */
    private static void regenerateFile(String filePath, FileType fileType) {
        File file = new File(filePath)

        if (fileType == FileType.FILE) {
            file.delete()
            if (Optional.ofNullable(file.parentFile).isPresent()) {
                file.createParentDirectories()
            }
            file.createNewFile()
        } else if (fileType == FileType.DIRECTORY) {
            file.deleteDir()
            file.createParentDirectories()
        }
    }

    static boolean hasDirectoryPath(String filePath) {
        return filePath.lastIndexOf('.') == -1 // true is directory path, false is file path
    }

    static boolean fileExists(String filePath) {
        return new File(filePath).exists()
    }
}

/**
 * Date and time util class.
 */
final class DateTimeUtil {
    enum Constants {
        FMT_YYYYMMDD_HHMMSS('yyyy-MM-dd HH:mm:ss'),
        ZONE_ID_UTC_GMT8('Asia/Shanghai')

        private String val

        Constants(String val) {
            this.val = val
        }

        String getVal() {
            return val
        }
    }

    /**
     *
     * @return
     */
    static Date getDateByNow() {
        ZonedDateTime zonedDateTime = LocalDateTime.now().atZone(ZoneId.of(Constants.ZONE_ID_UTC_GMT8.val))
        return Date.from(zonedDateTime.toInstant())
    }

    /**
     *
     * @param date
     * @param zoneId
     * @return
     */
    private static LocalDateTime dateToLocalDateTime(Date date, String zoneId) {
        Instant instant = date.toInstant()
        return instant.atZone(ZoneId.of(zoneId)).toLocalDateTime()
    }

    /**
     *
     * @param datePattern
     * @param date
     * @return
     */
    static String formatDate(String datePattern, Date date) {
        return DateTimeFormatter.ofPattern(datePattern)
                .format(dateToLocalDateTime(date, Constants.ZONE_ID_UTC_GMT8.val))
    }
}